/*
 * FTP daemon, complete rewrite using classes.
 * By Turrican@Discworld, 21-4-96.
 */

#include <network.h>

#define DELAY_LOG_FLUSH 5
#define PASS_FTP

inherit SERVER;

#include <ftp.h>
#include <localtime.h>

private nosave mapping socket_info, data_sockets;
private nosave mapping _log_file_info = ([ ]);
private nosave int _log_file_flush_id;

protected varargs string s_address(mixed first, int port) {
#ifdef PASS_FTP
    return socket_address(first,port);
#else
    return socket_address(first);
#endif
}

protected varargs int s_bind(int fd, int port, string str) {
#ifdef PASS_FTP
    return socket_bind(fd, port, str);
#else
    return socket_bind(fd, port);
#endif
}

protected void create() {
    server::create();
    seteuid("Root");
    socket_info = ([]);
    data_sockets = ([]);
    SetSocketType(STREAM);
    call_out("setup_ftp", 2);
} /* create() */

/**
 * This method flushes out all the buffered stuff for the log files.
 */
private void flush_log_files() {
    string fname;
    string data;

    _log_file_flush_id = 0;
    foreach (fname, data in _log_file_info) {
        map_delete(_log_file_info, fname);
        unguarded((: write_file, fname, data :));
    }
    _log_file_info = ([ ]);
} /* flush_log_files() */

void log_write(string name, string fmt, mixed *args ...) {
    if (!_log_file_flush_id) {
        _log_file_flush_id = call_out((: flush_log_files :), DELAY_LOG_FLUSH);
    }
    if (!_log_file_info[name]) {
        _log_file_info[name] = "";
    }
    if (sizeof(args)) {
        _log_file_info[name] += sprintf(fmt, args ...);
    } else {
        _log_file_info[name] += fmt;
    }
}

protected void setup_ftp() {
    int x;

    if ((x = eventCreateSocket(PORT_FTP)) < 0) {
        if (this_object()) destruct(this_object());
        return;
    }
    call_out("check_connections", 5*60);
} /* setup_ftp() */

/* returns an array of users connected to ftpd */
string *query_connections() {
    class session *vals, val;
    string *list;

    list = ({ });
    vals = (class session *)values(socket_info);
    foreach (val in vals) {
        if (val->user_name) {
            if (val->logged_in) {
                list += ({ capitalize(val->user_name) });
            } else {
                list += ({ "login" });
            }
        }
    }
    return list;
} /* query_connections() */

protected string ls(string path, int mask) {
    string *files, tmp, tmp2, creator, domain;
    int i, j, s, current_time;
    mixed *xfiles, *stats;

#ifdef DEBUG
    TPCRE(sprintf("ls(%s,%d)\n",path,mask));
#endif
    /* If the path is a directory, get contents uynless MASK_D is set */
    if (!(mask & MASK_D) && file_size(path) == -2) {
        if (path[ <1 ] == '/')
            path += "*";
        else
            path += "/*";
    }

    /* begin narrow columnar "nlst" */
    if (!(MASK_L & mask)) {
        files = get_dir(path);
        /* can only happen if permissions are messed up at account level */
        if (!files)
            return "";
        if (!(MASK_A & mask))
            files -= ({ ".", ".." });
        if (!(i = sizeof( files )))
            return "";
        /* no wild cards...must have been the exact pathname to a file */
        if (strsrch(path, '*') == -1 && strsrch(path, '?') == -1) {
            return files[0] + "\n";
        }
        /* remove globber at end of path, leave a trailing slash */
        j = strsrch(path, '/', -1);
        path = path[0..j];
        while (i--) {
            /* scan next level down for files */
            tmp = sprintf("%s%s/", path, files[i]);
            if (MASK_F & mask) {
                if (strsrch(tmp, "/./") != -1 || strsrch(tmp, "/../") != -1) {
                    files[i] += "/";
                    continue;
                }
                if (file_size(tmp) == -2)
                    files[i] += "/";
                else if (stat(tmp[0..<2])[2])
                    files[i] += "*";
            }
        }
        if (MASK_C & mask)
            return sprintf("%-#70s\n", implode(files, "\n"));
        else
            return implode(files, "\n") + "\n";
    }

    /* don't recurse */
    if (!(MASK_R & mask)) {
        /* begin long "list" */
        xfiles = get_dir(path, -1);
        if (!(mask & MASK_A))
            xfiles = filter_array(xfiles, "check_dots", this_object());
        if (!xfiles || !(s = sizeof( xfiles )))
            return "total 0\n";
        files = allocate(s);

        /* the Unix-like file permissions are mainly for effect...hopefully it
         * isn't too much, since anything more would likely be too cpu intensive
         * and cause it to max eval... */
        creator = (string)master()->author_file(path);
        if (!creator) {
            creator = "Root";
        }
        domain = (string)master()->domain_file(path);
        if (!domain) {
            domain = "Root";
        }
        i = strsrch(path, '/', -1);
        if (i >= 0)
            path = path[0..i];
        current_time = time();
        for (i = 0; i < s; i++) {
            /* process timestamp */
            tmp2 = ctime((xfiles[i])[2]); /* get last modified timestamp */
            if ((xfiles[i])[2] + (6 * 30 * 24 * 60 * 60) < current_time ||
              (xfiles[i])[2] - (60 * 60) > current_time ) {
                /* MMM DD  YYYY */
                tmp = sprintf("%s  %s", tmp2[4..9], tmp2[20..23]);
            } else {
                /* MMM DD hh:mm */
                tmp = tmp2[4..15];
            }
            j = (xfiles[i])[1];   /* get filesize */
            if (j == -2) {
                /* directory */
                files[i] = sprintf("drwxrwxr-x   0 %-8s %-8s        0 %12s %s",
                  creator, domain, tmp,
                  (xfiles[i])[0]+((MASK_F & mask)?"/":""));
            } else {
                /* file */
                stats = stat(path + (xfiles[i])[0]);
                files[i] = sprintf("-rw%crw-r--   1 %-8s %-8s %8d %12s %s",
                  sizeof(stats) > 1 && stats[2] ? 'x' : '-',
                  creator, domain, j, tmp,
                  (xfiles[i])[0] + (sizeof(stats) > 1 && stats[2] &&
                    (MASK_F & mask)?"*":""));
            }
        }
        return sprintf("total %i\n",s)+implode(files, "\n")+"\n";
    }
    /* begin long recursive "list" WARNING! still experimental */
    if( path[<1 .. <1] != "*" )
        return ls( path, (mask & ~MASK_R) );
    path = path[ 0 .. <3 ];
    files = ({ "" });
    tmp = "";
    while( sizeof( files ) ) {
        reset_eval_cost();
        if( files[ 0 ] == "" )
            tmp += ls( path + "/*", (mask & ~MASK_R) );
        else
            tmp += "\n" +files[ 0 ][ 1 .. <1 ] + ":\n" +
            ls( path + files[ 0 ] + "/*", (mask & ~MASK_R) );
        xfiles = get_dir( path + files[ 0 ] + "/*", -1 );
        if( xfiles && ( s = sizeof( xfiles ) ) ) {
            for (i = 0; i < s; i++) {
                j = (xfiles[i])[1];   /* get filesize */
                if ((j == -2) && (xfiles[i][0] != ".") && (xfiles[i][0] != "..")) {
                    files += ({ files[ 0 ] +"/"+ xfiles[i][0] });
                }
            }
        }
        files = files[ 1 .. <1 ];
    }
    return tmp;
} /* ls() */

protected void data_conn(int fd, string mess, string name, int type) {
    int new_fd, ret, data_mode;
    string data_mode_name, addr;
    class session sess = (class session)socket_info[fd];
    class dataconn t;

    if (type == STRING || (type == FILE && sess->type == STRING)) {
        data_mode_name = "ASCII";
        data_mode = STREAM;
    } else {
        data_mode_name = "BINARY";
        data_mode = STREAM_BINARY;
    }

    t = new(class dataconn);
    t->path = name;
    t->data = (type == STRING ? replace_string(mess, "\n", "\r\n") : mess);
    t->pos = sess->offset;
    t->parent_fd = fd;
    t->type = type;
    t->len = (type == STRING ? strlen(mess) : file_size(mess));

    if (sess->pasv_fd != -1) {
        if (sess->pasv_cb) {
            TPCRE("Accepting after delay...\n");
            new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
              "data_write_callback");
            if (new_fd < 0) {
                eventWrite(fd, "425 Can't open data connection.\r\n");
                socket_close(sess->pasv_fd);
                sess->pasv_fd = -1;
                return;
            }
            socket_close(sess->pasv_fd);
            sess->pasv_fd = new_fd;
        } else {
            TPCRE("No connection yet...\n");
            data_sockets[sess->pasv_fd] = t;
            return;
        }
    } else {
        sess->use_default = 1;

        if (sess->data_fd != -1) {
            eventWrite(fd, "425 Can't open data connection.\r\n");
            return;
        }
        new_fd = socket_create(data_mode, "data_read_callback",
          "data_close_callback");

        if (new_fd < 0) {
            eventWrite(fd, "425 Can't create data socket.\r\n");
            return;
        }

        if (!sess->data_addr) {
            eventWrite(fd, "425 Can't open data connection.\r\n");
            socket_close(new_fd);
            return;
        }

        sscanf(s_address(fd, 1), "%s %*d", addr);
        addr = sprintf("%s %d", addr, (PORT_FTP - 1));

        TPCRE(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
        if ((ret = s_bind(new_fd, 0, addr)) < 0) {
            eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
                socket_error(ret)));
            socket_close(new_fd);
            return;
        }
    }

    data_sockets[new_fd] = t;
    sess->data_fd = new_fd;
    if (sess->pasv_fd == -1 &&
      (ret = socket_connect(new_fd,
          sprintf("%s %d", sess->data_addr, sess->data_port),
          "data_read_callback",
          "data_write_callback")) < 0) {
        TPCRE("Error: " + sess->data_addr + " " + sess->data_port + "\n");
        TPCRE(socket_error(ret) + "\n");
        eventWrite(fd, "425 Can't build data connection.\r\n");
        sess->data_fd = -1;
        socket_close(new_fd);
        map_delete(data_sockets, new_fd);
        return;
    }
    eventWrite(fd, sprintf("150 Opening %s mode data connection for %s "
        "(%d bytes).\r\n", data_mode_name, name, t->len));
    if (sess->pasv_fd != -1)
        data_write_callback(new_fd);
} /* data_conn() */

protected void read_connection(int fd, string path, int append) {
    int new_fd, ret, data_mode;
    string data_mode_name, opath, addr;
    class dataconn t;
    class session sess = (class session)socket_info[fd];

    if (sess->type == BINARY) {
        data_mode_name = "BINARY";
        data_mode = STREAM_BINARY;
    } else {
        data_mode_name = "ASCII";
        data_mode = STREAM;
    }

    opath = path;
    if (append != 1) {
        path = path + ".ftptmp";
        if (file_size(path) > -1)
            catch(rm(path));
    }

    t = new(class dataconn);
    t->path = path;
    t->parent_fd = fd;
    t->pos = (!append?0:(file_size(opath)==-1?0:file_size(opath)));
    t->type = DOWNLOAD;
    t->append = append;

    if (sess->pasv_fd != -1) {
        if (sess->pasv_cb) {
            new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
              "data_write_callback");
            if (new_fd < 0) {
                eventWrite(fd, "425 Can't open data connection.\r\n");
                socket_close(sess->pasv_fd);
                sess->pasv_fd = -1;
                return;
            }
            socket_close(sess->pasv_fd);
            sess->pasv_fd = new_fd;
        } else {
            data_sockets[sess->pasv_fd] = t;
            return;
        }
    } else {
        sess->use_default = 1;
        if (sess->data_fd != -1) {
            eventWrite(fd, "425 Can't open data connection.\r\n");
            return;
        }

        new_fd = socket_create(data_mode, "data_read_callback",
          "data_close_callback");

        if (new_fd < 0) {
            eventWrite(fd, "425 Can't create data socket.\r\n");
            return;
        }

        sscanf(s_address(fd, 1), "%s %*d", addr);
        addr = sprintf("%s %d", addr, (PORT_FTP - 1));

        TPCRE(sprintf("s_bind(%d, 0, %s)\n", new_fd, addr));
        if ((ret = s_bind(new_fd, 0, addr)) < 0) {
            eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
                socket_error(ret)));
            socket_close(new_fd);
            return;
        }
    }

    data_sockets[new_fd] = t;
    sess->data_fd = new_fd;
    if (sess->pasv_fd == -1 &&
      (ret = socket_connect(new_fd, sprintf("%s %d", sess->data_addr,
            sess->data_port),
          "data_read_callback",
          "data_write_callback")) < 0) {
        TPCRE("Error: " + sess->data_addr + " " + sess->data_port + "\n");
        TPCRE(socket_error(ret) + "\n");
        eventWrite(fd, "425 Can't build data connection.\r\n");
        sess->data_fd = -1;
        socket_close(new_fd);
        map_delete(data_sockets, new_fd);
        return;
    }
    eventWrite(fd, sprintf("150 Opening %s mode data connection for %s.\r\n",
        data_mode_name, opath));
} /* read_connection() */

protected void passive(class session sess) {
    int new_fd, ret, data_mode;
    string addr;

    if (sess->pasv_fd != -1) {
        /* Already in passive mode... */
        eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
            replace_string(sess->data_addr, ".", ","),
            sess->data_port>>8, sess->data_port & 0xff));
        return;
    }

    if (sess->type == BINARY)
        data_mode = STREAM_BINARY;
    else
        data_mode = STREAM;

    new_fd = socket_create(data_mode, "data_read_callback",
      "data_close_callback");
    if (new_fd < 0) {
        eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
        return;
    }

    /* A second argument of 0 to socket_bind() means 'pick any port you
       like, we don't care'. Unfortunately, there's no way to get
       the port number back... */

    sscanf(s_address(sess->fd, 1), "%s %*d", addr);
    addr = sprintf("%s %d", addr, 0);

    TPCRE(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
    if ((ret = s_bind(new_fd, 0, addr)) < 0) {
        eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
        socket_close(new_fd);
        return;
    }

    if ((ret = socket_listen(new_fd, "data_listen_callback")) < 0) {
        eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
        socket_close(new_fd);
        return;
    }

    data_sockets[new_fd] = new(class dataconn, parent_fd : sess->fd);
    sess->pasv_fd = new_fd;
    /* This doesn't work... It returns all zeroes. */
    sscanf(s_address(new_fd, 1), "%s %d", sess->data_addr, sess->data_port);
    eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
        replace_string(sess->data_addr, ".", ","),
        sess->data_port>>8, sess->data_port & 0xff));
} /* passive() */

protected void data_listen_callback(int fd) {
    class dataconn dc = (class dataconn)data_sockets[fd];
    class session sess;
    int new_fd;
    string data_mode_name;

    if (!classp(dc)) {
        /* Hm. No longer around. Oh well. */
        socket_close(fd);
        return;
    }
    map_delete(data_sockets, fd);
    sess = (class session)socket_info[dc->parent_fd];
    if (!classp(sess)) {
        socket_close(fd);
        return;
    }
    if (dc->type) {
        TPCRE("Accepting...\n");
        if (sess->type == BINARY)
            data_mode_name = "BINARY";
        else
            data_mode_name = "ASCII";

        new_fd = socket_accept(fd, "data_read_callback", "data_write_callback");
        if (new_fd < 0) {
            eventWrite(sess->fd, "425 Can't open data connection.\r\n");
            socket_close(fd);
            sess->pasv_fd = -1;
            return;
        }
        socket_close(fd);
        sess->pasv_fd = new_fd;
        sess->data_fd = new_fd;
        data_sockets[new_fd] = dc;
        if (dc->type == DOWNLOAD)
            eventWrite(sess->fd,
              sprintf("150 Opening %s mode data connection for %s.\r\n",
                data_mode_name, dc->path[0..<8]));
        else {
            eventWrite(sess->fd,
              sprintf("150 Opening %s mode data connection for %s "
                "(%d bytes).\r\n", data_mode_name, dc->path,
                dc->len));
            data_write_callback(new_fd);
        }
    } else {
        TPCRE("Delaying...\n");
        sess->pasv_cb = 1;
        return;
    }
    /* We delay accepting the connection until later. */
}

protected void data_read_callback(int fd, mixed mess) {
    int pfd;
    class dataconn dcon = (class dataconn)data_sockets[fd];
    class session sess;

    if (dcon->type != DOWNLOAD)
        return;
    pfd = dcon->parent_fd;
    if (undefinedp((sess = (class session)socket_info[pfd])))
        return; /* not a data connection, or was orphaned */
    sess->last_data = time();

    if (stringp(mess))
        mess = replace_string(mess, "\r", "");

#ifdef DEBUG_RECEIVE
    TPCRE("received from " + dcon->pos + " size " +
      (stringp(mess)?strlen(mess):sizeof(mess)) + ".\n");
#endif
    write_buffer(dcon->path, dcon->pos, mess);
    dcon->pos += (stringp(mess)?strlen(mess):sizeof(mess));
} /* data_read_callback() */

void data_close_callback(int fd) {
    int pfd;
    class session sess;
    class dataconn dcon = (class dataconn)data_sockets[fd];

    if (!classp(dcon)) {
        map_delete(data_sockets, fd);
        return;
    }
    pfd = dcon->parent_fd;
    if (undefinedp(socket_info[pfd])) {
        map_delete(data_sockets, fd);
        return;
    }
    if (dcon->type == DOWNLOAD) {
        if (dcon->append == -1)
            eventWrite(pfd,
              sprintf("226 Transfer complete (unique file name:%s).\r\n",
                dcon->path));
        else if (dcon->append) {
            eventWrite(pfd, "226 Transfer complete.\r\n");
        } else {
            eventWrite(pfd, "226 Transfer complete.\r\n");
            catch(rm(dcon->path[0..<8]));
            catch(rename(dcon->path, dcon->path[0..<8]));
        }
    } else if (!dcon->type) {
        map_delete(data_sockets, fd);
        return;
    }
    /*
     * only close data connections here
     */
    sess = (class session)socket_info[pfd];

    sess->data_fd = -1;
    sess->pasv_fd = -1;
    sess->offset = 0;
    map_delete(data_sockets, fd);
#ifdef DEBUG_RECEIVE
    TPCRE("dcc() complete, exiting.\n");
#endif
} /* data_close_callback() */

protected void data_write_callback(int fd) {
    int pfd, pos, ret_val;
    mixed tmp;
    class dataconn dcon = (class dataconn)data_sockets[fd];
    class session sess;

    if (dcon->type == DOWNLOAD)
        return;

    pos = dcon->pos;

    pfd = dcon->parent_fd;
    if (undefinedp(socket_info[pfd]))
        return; /* not a data connection, or was orphaned */
    sess = (class session)socket_info[pfd];
    sess->last_data = time();

    if (pos > dcon->len || dcon->len == 0) {
        TPCRE("pos > len\n");
        eventWrite(pfd, "226 Transfer complete.\r\n");
        socket_close(fd);
        map_delete(data_sockets, fd);
        sess->data_fd = -1;
        sess->pasv_fd = -1;
        sess->offset = 0;
#ifdef DEBUG_SEND
        TPCRE("dwc() complete, exiting.\n");
#endif
        return;
    }

#ifdef DEBUG_SEND
    TPCRE("Entering dwc(), pos: " + pos + " length should be: " + BLOCK_SIZE + ".\n");
#endif

    if (dcon->type == STRING) {
#ifdef DEBUG_SEND
        TPCRE("type == STRING\n");
#endif
        while ((ret_val = socket_write(fd, dcon->data[pos..(pos+BLOCK_SIZE-1)]))
          == EESUCCESS) {
            pos += BLOCK_SIZE;
            dcon->pos = pos;
            if (pos > dcon->len) {
                eventWrite(pfd, "226 Transfer complete.\r\n");
                socket_close(fd);
                map_delete(data_sockets, fd);
                sess->data_fd = -1;
                sess->pasv_fd = -1;
                sess->offset = 0;
#ifdef DEBUG_SEND
                TPCRE("dwc() complete, exiting.\n");
#endif
                return;
            }
        }
    } else {
#ifdef DEBUG_SEND
        TPCRE("type is other then STRING\n");
#endif
        if (CHECK_STRING) {
            tmp = "";
            if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
                eventWrite(pfd, "551 Error on input file.\r\n");
            tmp = replace_string(tmp, "\n", "\r\n");
        } else {
            tmp = allocate_buffer(0);
            if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
                eventWrite(pfd, "551 Error on input file.\r\n");
        }

        while ((ret_val = socket_write(fd, tmp)) == EESUCCESS) {
#ifdef DEBUG_SEND
            TPCRE("sent from " + pos + " to " + (pos + BLOCK_SIZE) + ".\n");
            TPCRE("ret_val was: " + ret_val + ".\n");
#endif

            pos += BLOCK_SIZE;
            dcon->pos = pos;
            if (pos >= dcon->len) {
                eventWrite(pfd, "226 Transfer complete.\r\n");
                socket_close(fd);
                map_delete(data_sockets, fd);
                sess->data_fd = -1;
                sess->pasv_fd = -1;
                sess->offset = 0;
#ifdef DEBUG_SEND
                TPCRE("dwc() complete, exiting.\n");
#endif
                return;
            }
            if (CHECK_STRING) {
                tmp = "";
                if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
                    eventWrite(pfd, "551 Error on input file.\r\n");
                tmp = replace_string(tmp, "\n", "\r\n");
            } else {
                tmp = allocate_buffer(0);
                if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
                    eventWrite(pfd, "551 Error on input file.\r\n");
            }
        }
    }
#ifdef DEBUG_SEND
    TPCRE("ret_val was: " + ret_val + ".\n");
    TPCRE("leaving dwc(), pos: " + pos + ".\n");
#endif

    if (ret_val == EEWOULDBLOCK) {
        /* it would block, so it's up to us to try again */
#ifdef DEBUG_SEND
        TPCRE("Adding call_out\n");
#endif
        call_out("data_write_callback", 1, fd);
    } else if (ret_val == EECALLBACK) {
        /* Buffer full, wait untill we are called back again. Do increase the
         * position, since the previous block WAS sent.
         * We are now flow controlled. */
        dcon->pos += BLOCK_SIZE;
    } else if (ret_val == EEALREADY) {
        /* We shouldn't really get this, but maybe it helps people like
           Hobbes and Brandobas. The driver will call us again. */
        return;
    } else {
        /* not going to be called again by driver */
        while (remove_call_out("data_write_callback") != -1) {
#ifdef DEBUG_SEND
            TPCRE("Killing callout.\n");
#endif
        }
    }
} /* data_write_callback() */

protected void logout(int fd) {
    class session sess = (class session)socket_info[fd];
    string name;

    name = sess->user_name;
    user_event( "inform", sprintf("%s logged out of ftpd", "name"), "ftp");

#ifdef LOG_CONNECT
    log_write(LOG_FILENAME,
      sprintf("%s logged out at %s.\n", name, ctime(time())));
#endif

    sess->user_name = sess->logged_in = sess->cwd = 0;
} /* logout() */

protected void eventNewConnection(int fd) {
    class session t;

    server::eventNewConnection(fd);
    t = new(class session);
    t->fd = fd;
    t->user_name = "Login";
    t->idle = 900;
    t->last_data = time();
    t->data_fd = -1;
    t->pasv_fd = -1;
    sscanf(s_address(fd), "%s %d", t->data_addr, t->data_port);
    t->use_default = 1;
    socket_info[fd] = t;
    eventWrite(fd, sprintf("220 %s FTP server ready.  "
        "Please login as yourself.\r\n", mud_name()));
}

protected void parse_comm(int fd, string str) {
    string *bits, tmp, cmd, rest, rest2;
    mixed *misc;
    int port, i, mask;
    class session sess;

    if (strsrch(lower_case(str), "pass") == -1) {
        i = 0;
        TPCRE("Parsing " + str + ".\n");
    }
    bits = explode(str, " ");
    cmd = bits[ 0 ];
    if (sizeof( bits ) > 1 )
        rest = implode( bits[1 .. ], " " );
    else
        rest = "";
    sess = (class session)socket_info[fd];
    sess->last_data = time();

    switch (lower_case(bits[0])) {
    case "port":
        bits = explode(rest, ",");
        if (sizeof(bits) < 6) {
            eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
        } else {
            sess->data_addr = implode(bits[0..3], ".");
            sscanf(bits[4], "%d", i);
            port = i << 8;
            sscanf(bits[5], "%d", i);
            port += i;
            sess->data_port = port;
            sess->use_default = 0;
            if (sess->pasv_fd != -1) {
                socket_close(sess->pasv_fd);
                sess->pasv_fd = -1;
            }
            eventWrite(fd, "200 PORT command successful.\r\n");
        }
        break;
    case "user":
        CHECK_CMD(1);
        if ((bits[1] == "offler") && sess->logged_in)
            if (lordp(sess->user_name)) {
                SHUTDOWN_H->init_shutdown(10,1);
                if (SHUTDOWN_H->query_shutdown()<10)
                    eventWrite(fd, "530 Offler loaded.\r\n");
                else
                    eventWrite(fd, "530 Offler failed to load.\r\n");
                break;
            }
        if (sess->logged_in)
            logout(fd);
        if (!LOGIN_OB->test_user(bits[1]))
            eventWrite(fd, sprintf("530 User %s access denied...\r\n", bits[1]));
        else {
            eventWrite(fd, sprintf("331 Password required for %s.\r\n", bits[1]));
            sess->user_name = bits[1];
        }
        break;
    case "pass":
        if (sess->logged_in || !sess->user_name) {
            eventWrite(fd, "503 Login with USER first.\r\n");
            break;
        }
        if (!LOGIN_OB->test_password(sess->user_name, rest)) {
            TCRE("shaydz","failed");
            event( users(),"inform", sprintf("%s failed to login, ftp",
                sess->user_name), "bad-password");
            eventWrite(fd, "530 Login incorrect.\r\n");
            sess->user_name = "Login";
            break;
        } else if (!creatorp(sess->user_name)) {
            sess->logged_in = 2;
            sess->cwd = "/open";
            sess->type = STRING;
            user_event( "inform", sprintf("%s(player) connected to ftpd",
                sess->user_name), "ftp");
#ifdef LOG_CONNECT
            log_write(LOG_FILENAME, sprintf("%s(player) connected at %s.\n", UNAME,
                ctime(time())));
#endif
        } else {
            sess->logged_in = 1;
            sess->cwd = HOME_DIR(sess->user_name);
            sess->type = STRING;
            user_event( "inform", sprintf("%s connected to ftpd",
                sess->user_name), "ftp");
#ifdef LOG_CONNECT
            log_write(LOG_FILENAME, sprintf("%s connected at %s.\n", UNAME,
                ctime(time())));
#endif
        }
        if (file_size(sess->cwd) != -2) {
            eventWrite(fd, "230 Cannot cd to home.  Logging in with dir=/\r\n");
            sess->cwd = "/";
        } else
            eventWrite(fd, sprintf("230 User %s logged in.\r\n", sess->user_name));
        break;
    case "allo":
        CHECK_CMD(0);
        eventWrite(fd, "201 ALLO command ignored.\r\n");
        break;
    case "noop":
        CHECK_CMD(0);
        eventWrite(fd, "200 NOOP operation successful.\r\n");
        break;
    case "rnfr":
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (!master()->valid_read(tmp, sess->user_name, "file_size")) {
            eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
                rest));
            break;
        }
        if (file_size(tmp) != -1) {
            sess->rnfr = tmp;
            eventWrite(fd, "350 File exists, ready for destination name\r\n");
        } else
            eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                rest));
        break;
    case "rnto":
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        if (!sess->rnfr) {
            eventWrite(fd, "503 Bad sequence of commands.\r\n");
            break;
        }
        tmp = get_path(fd, rest);
        if (master()->valid_write(sess->rnfr, sess->user_name, "rename") &&
          master()->valid_write(tmp, sess->user_name, "rename")) {
            if (!catch(rename(sess->rnfr, tmp)))
                eventWrite(fd, "250 RNTO command successful.\r\n");
            else
                eventWrite(fd, "550 rename: No such file or directory.\r\n");
        } else
            eventWrite(fd, "550 rename: Operation not permitted.\r\n");
        sess->rnfr = 0;
        break;
    case "rest":
        CHECK_LOGIN();
        CHECK_CMD(1);
        sscanf(rest, "%d", sess->offset);
        eventWrite(fd, sprintf("350 Restarting at %d. %s\r\n", sess->offset,
            "Send STORE or RETRIEVE to initiate transfer."));
        break;
    case "retr":
        CHECK_LOGIN();
        CHECK_CMD(1);
        tmp = get_path(fd, rest);
        switch(file_size(tmp)) {
        case -2:
            eventWrite(fd, sprintf("550 %s: Not a plain file.\r\n", rest));
            break;
        case -1:
            eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                rest));
            break;
        default:
            if (!master()->valid_read(tmp, sess->user_name, "read_file"))
                eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
                    rest));
            else {
#ifdef LOG_FILE
                log_write(LOG_FILENAME, sprintf("%s RETR %s at %s.\n", UNAME, tmp,
                    ctime(time())));
#endif
                data_conn(fd, tmp, rest, FILE);
            }
            break;
        }
        break;
    case "stor":
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
            log_write(LOG_FILENAME, sprintf("%s STOR %s at %s.\n", UNAME, tmp,
                ctime(time())));
#endif
            if (sess->offset)
                read_connection(fd, tmp, 1);
            else
                read_connection(fd, tmp, 0);
        } else
            eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
        break;
    case "dele":
        /* delete a file */
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (master()->valid_write(tmp, sess->user_name, "rm")) {
            if (file_size(tmp) == -1) {
                eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                    rest));
                break;
            }
#ifdef LOG_FILE
            log_write(LOG_FILENAME, sprintf("%s DELE %s at %s.\n", UNAME, tmp,
                ctime(time())));
#endif
            if (!rm(tmp))
                eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
                    rest));
            else
                eventWrite(fd, "250 DELE command successful.\r\n");
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "mkd":
    case "xmkd":
        /* make a dir */
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (master()->valid_write(tmp, sess->user_name, "mkdir")) {
#ifdef LOG_FILE
            log_write(LOG_FILENAME, sprintf("%s MKD %s at %s.\n", UNAME, tmp,
                ctime(time())));
#endif
            if (!mkdir(tmp))
                eventWrite(fd, sprintf("550 %s: File exists.\r\n", rest));
            else
                eventWrite(fd, "257 MKD command successful.\r\n");
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "rmd":
    case "xrmd":
        /* remove a dir */
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (master()->valid_write(tmp, sess->user_name, "rmdir")) {
            if (file_size(tmp) == -1) {
                eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                    rest));
                break;
            }
            if (file_size(tmp) != -2) {
                eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
                break;
            }
            if (!rmdir(tmp))
                eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
                    rest));
            else {
#ifdef LOG_FILE
                log_write(LOG_FILENAME, sprintf("%s RMD %s at %s.\n", UNAME, tmp,
                    ctime(time())));
#endif
                eventWrite(fd, "250 RMD command successful.\r\n");
            }
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "appe":
        /* append... */
        CHECK_LOGIN();
        CHECK_CMD(1);
        CHECK_PLAYER();
        tmp = get_path(fd, rest);
        if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
            log_write(LOG_FILENAME, sprintf("%s APPE %s at %s.\n", UNAME, tmp,
                ctime(time())));
#endif
            read_connection(fd, tmp, 1);
        } else
            eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
        break;
    case "help":
        if (sizeof(bits) > 1) {
            tmp = lower_case(bits[1]);
            if (tmp == "site")
                bits[1] = "HELP";
            if (!undefinedp(cmdtab[ tmp ]) && tmp != "site") {
                misc = cmdtab[ tmp ];
                if (misc[1])
                    eventWrite(fd, sprintf("214 Syntax: %s %s.\r\n", misc[0], misc[2]));
                else
                    eventWrite(fd, sprintf("214 %s %s; unimplemented.\r\n",
                        misc[0], misc[2]) );
                break;
            } else if (bits[1] != "HELP") {
                eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[1]));
                break;
            }
        } else {
            int s;
            eventWrite(fd, "214-The following commands are recognized "
              "(* =>'s unimplemented).\r\n");
            misc = keys(cmdtab);
            s = sizeof(misc);
            tmp = "   ";
            for (i = 0; i < s; i++) {
                tmp += sprintf("%-4s%-4s", cmdtab[misc[i]][0],
                  cmdtab[misc[i]][1] ? " " : "*");
                if (i % 8 == 7) {
                    eventWrite(fd, tmp + "\r\n");
                    tmp = "   ";
                }
            }
            if (i % 8)
                eventWrite(fd, tmp + "\r\n");
            eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
                "Turrican@Discworld"));
            break;
        }
    case "site":
        CHECK_LOGIN();
        if (sizeof( bits ) > 2 )
            rest2 = implode( bits[2 .. ], " " );
        else
            rest2 = "";
        switch (lower_case(bits[1])) {
        case "idle":
            if (sizeof(bits) < 3) {
                eventWrite(fd,
                  sprintf("200 Current IDLE time limit is %d seconds; max 7200\r\n",
                    sess->idle));
                break;
            }

            if (!sscanf(rest2, "%d", i)) {
                eventWrite(fd, "550 SITE IDLE command failed.\r\n");
                break;
            }

            i = (i<300?300:(i>7200?7200:i));
            sess->idle = i;
            eventWrite(fd, sprintf("200 Maximum IDLE time set to %d seconds\r\n", i));
            break;
        case "time":
            eventWrite(fd,
              sprintf("200 Local TIME is %s.\r\n", ctime(time())[4..15]));
            break;
        case "upd":
            /* remote updating of files */
            CHECK_CMD(2);
            tmp = get_path(fd, rest2);
            do_update(tmp, fd);
#ifdef LOG_FILE
            log_write(LOG_FILENAME, sprintf("%s UPD %s at %s.\n", UNAME, tmp,
                ctime(time())));
#endif
            break;
        case "help":
            if (sizeof(bits) > 2) {
                tmp = lower_case(bits[2]);
                if (!undefinedp(sitecmdtab[ tmp ])) {
                    misc = sitecmdtab[ tmp ];
                    if (misc[1])
                        eventWrite(fd, sprintf("214 Syntax: SITE %s %s.\r\n",
                            misc[0], misc[2]) );
                    else
                        eventWrite(fd, sprintf("214 SITE %s %s; unimplemented.\r\n",
                            misc[0], misc[2]) );
                } else {
                    eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[2]));
                }
            } else {
                int s;
                eventWrite(fd, "214-The following SITE commands are recognized "
                  "(* =>'s unimplemented).\r\n");
                misc = keys(sitecmdtab);
                s = sizeof(misc);
                tmp = "   ";
                for (i = 0; i < s; i++) {
                    tmp += sprintf("%-*s%-*s", strlen(sitecmdtab[misc[i]][0]),                        sitecmdtab[misc[i]][0], 8-strlen(sitecmdtab[misc[i]][0]),
                      sitecmdtab[misc[i]][1] ? " " : "*");
                    if (i % 8 == 7) {
                        eventWrite(fd, tmp + "\r\n");
                        tmp = "   ";
                    }
                }
                if (i % 8)
                    eventWrite(fd, tmp + "\r\n");
                eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
                    "Turrican@Discworld"));
            }
            break;
        case "newer":
        case "minfo":
            eventWrite(fd, sprintf("502 %s command not implemented.\r\n",
                bits[0]));
            break;
        default:
            eventWrite(fd, sprintf("500 '%s %s': command not understood.\r\n",
                bits[0], bits[1]));
            break;
        }
        break;
    case "mdtm":
        /* Supposed to return modified time in the format: YYYYMMDDHHMMSS */
        CHECK_LOGIN();
        CHECK_CMD(1);
        tmp = get_path(fd, rest);
        if (master()->valid_read(tmp, sess->user_name, "file_size")) {
            if (file_size(tmp) == -2)
                eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
            else if (file_size(tmp) == -1)
                eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
            else {
                mixed *tm;

                tm = localtime(stat(tmp)[1]+localtime(0)[LT_GMTOFF]);
                eventWrite(fd, sprintf("213 %d%02d%02d%02d%02d%02d\r\n",
                    tm[LT_YEAR], tm[LT_MON]+1, tm[LT_MDAY],
                    tm[LT_HOUR], tm[LT_MIN], tm[LT_SEC]));
            }
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "size":
        CHECK_LOGIN();
        CHECK_CMD(1);
        tmp = get_path(fd, rest) ;
        if (master()->valid_read(tmp, sess->user_name, "file_size")) {
            i = file_size(tmp);
            if (i == -2)
                eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
            else if (i == -1)
                eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
            else {
#ifdef LOG_CD_SIZE
                log_write(LOG_FILENAME, sprintf("%s SIZE %s at %s.\n", UNAME, tmp,
                    ctime(time())));
#endif
                eventWrite(fd, sprintf("213 %d\r\n", i));
            }
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "stat":
        if (sizeof(bits) > 1) {
            CHECK_LOGIN();
            tmp = get_path(fd, rest);
            if (master()->valid_read(tmp, sess->user_name, "get_dir")) {
                if (file_size(tmp) != -1)
                    eventWrite(fd, sprintf("211-status of %s:\r\n%s"
                        "211 End of status\r\n", rest,
                        ls(tmp, MASK_L)));
                else
                    eventWrite(fd, sprintf("211 %s: No such file or directory.\r\n",
                        rest));
            } else
                eventWrite(fd, sprintf("211 Permission denied to %s.\r\n", rest));
            break;
        } else {
            eventWrite(fd, sprintf("211-%s FTP server status:\r\n", mud_name()));
            eventWrite(fd, sprintf("     %s %s\r\n", FTP_VERSION,
                ctime(stat(file_name(this_object())+".c")[1])));
            sscanf(s_address(fd), "%s %*d", tmp);
            eventWrite(fd, sprintf("     Connected to %s\r\n", tmp));
            if (sess->logged_in)
                eventWrite(fd, sprintf("     Logged in as %s\r\n", sess->user_name));
            else if (sess->user_name)
                eventWrite(fd, "     Waiting for password\r\n");
            else
                eventWrite(fd, "     Waiting for user name\r\n");
            eventWrite(fd, sprintf("     TYPE: %s, FORM: Nonprint; STRUcture: "
                "File; transfer MODE: Stream\r\n",
                (sess->type == STRING?"ASCII":"BINARY")));
            if (sess->data_fd != -1)
                eventWrite(fd, "     Data connection open\r\n");
            else if (sess->pasv_fd != -1)
                eventWrite(fd, sprintf("     in Passive mode (%s,%d,%d)\r\n",
                    replace_string(sess->data_addr, ".", ","),
                    sess->data_port>>8, sess->data_port & 0xff));
            else if (!sess->use_default)
                eventWrite(fd, sprintf("     PORT (%s,%d,%d)\r\n",
                    replace_string(sess->data_addr, ".", ","),
                    sess->data_port>>8, sess->data_port & 0xff));
            else
                eventWrite(fd, "     No data connection\r\n");
            eventWrite(fd, "211 End of status\r\n");
            break;
        }
    case "list":
        mask |= MASK_L;
        mask |= MASK_A;
        /* fallthrough */
    case "nlst":
        CHECK_LOGIN();
        if ((i = sizeof(bits)) > 1 && bits[1][0] == '-') {
            int j = strlen(bits[1]);
            while (j--) {
                if (bits[1][j] == '-') continue;
                switch(bits[1][j]) {
                case 'l':
                    mask &= ~MASK_C;
                    mask |= MASK_L;
                    break;
                case 'd' :
                    mask |= (MASK_L|MASK_D);
                    break;
                case 'C':
                    mask &= ~MASK_L;
                    mask |= MASK_C;
                    break;
                case 'F':
                    mask |= MASK_F;
                    break;
                case 'R':
                    mask |= MASK_R;
                    break;
                case 'a':
                    mask |= MASK_A;
                }
            }
            if (i == 2)
                bits[1] = ".";
            else
                bits = ({ bits[0] }) + bits[2..i-1];
        }
        if (sizeof(bits) > 1)
            tmp = get_path(fd, implode( bits[1..], " "));
        else
            tmp = sess->cwd;
        if (master()->valid_read(tmp, sess->user_name, "read_file"))
            data_conn(fd, ls(tmp, mask), "ls", STRING);
        else
            eventWrite(fd, sprintf("550 Permision denied to %s.\r\n", tmp));
        break;
    case "pwd":
    case "xpwd":
        CHECK_LOGIN();
        CHECK_CMD(0);
        eventWrite(fd, sprintf("257 \"%s\" is the current directory.\r\n",
            sess->cwd));
        break;
    case "cdup":
    case "xcup":
        CHECK_CMD(0);
        bits += ({".."});
        rest = "..";
    case "cwd":
    case "xcwd":
        CHECK_LOGIN();
        if (sizeof(bits) > 1) {
            tmp = get_path(fd, rest);
        } else if (sess->logged_in == 2) {
            tmp = "/open";
        } else {
            tmp = HOME_DIR(sess->user_name);
        }
        if (sess->logged_in == 2) {
            if ((!((tmp[0..strlen("/open/")-1] == "/open/") || (tmp == "/open")) ||
                (tmp[0..strlen("/open/boards")-1] == "/open/boards"))) {
                eventWrite(fd, "553 Permission denied (you are not a creator)\r\n");
                break;
            }
        }
        if (master()->valid_read(tmp, sess->user_name, "cwd") || tmp == "/") {
            switch(file_size(tmp)) {
            case -2:
#ifdef LOG_CD_SIZE
                log_write(LOG_FILENAME, sprintf("%s CWD %s at %s.\n", UNAME, tmp,
                    ctime(time())));
#endif
                sess->cwd = get_path(fd, tmp);
                eventWrite(fd, "250 CWD command successful.\r\n");
                break;
            case -1:
                eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                    rest));
                break;
            default:
                eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
                break;
            }
        } else
            eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
        break;
    case "quit":
        CHECK_CMD(0);
        eventWrite(fd, "221 Goodbye.\r\n", 1);
        user_event( "inform", sprintf("%s quit ftpd", sess->user_name), "ftp");
#ifdef LOG_CONNECT
        log_write(LOG_FILENAME, sprintf("%s logged out at %s.\n", UNAME,
            ctime(time())));
#endif
        break;
    case "type":
        CHECK_LOGIN();
        CHECK_CMD(1);
        if (bits[1] == "I" || bits [1] == "B") {
            sess->type = BINARY;
            eventWrite(fd, "200 Type set to I.\r\n");
        } else if (bits[1] == "A") {
            sess->type = STRING;
            eventWrite(fd, "200 Type set to A.\r\n");
        } else
            eventWrite(fd, sprintf("504 Type %s not implemented.\r\n", bits[1]));
        break;
    case "abor":
        /* Abort...  Handle this with blue stuff,
         * stops recvs and stors. I guess thats
         * what it is supposed to do. */
        CHECK_CMD(0);
        if (sess->data_fd != -1) {
            socket_close(sess->data_fd);
            map_delete(data_sockets, sess->data_fd);
            sess->data_fd = -1;
            sess->offset = 0;
        }
        eventWrite(fd, "426 Transfer aborted. Data connection closed.\r\n");
        eventWrite(fd, "225 ABOR command successful.\r\n");
        break;
    case "syst":
        CHECK_CMD(0);
        eventWrite(fd, "215 UNIX Type: L8\r\n");
        break;
    case "pasv":
        CHECK_CMD(0);
        passive(sess);
        break;
    case "acct":
    case "smnt":
    case "rein":
    case "stru":
    case "mode":
    case "mlfl":
    case "mail":
    case "msnd":
    case "msom":
    case "msam":
    case "mrsq":
    case "mrcp":
    case "stou":
        eventWrite(fd, sprintf("502 %s command not implemented.\r\n", bits[0]));
        break;
    default:
        eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
        break;
    }
} /* parse_comm */

protected void eventRead(int fd, string str) {
    string *bits, bit;

    str = replace(str,
      ({sprintf("%c", 242), "", "\r", "", sprintf("%c", 255), "",
        sprintf("%c", 244), ""}));
    bits = explode(str, "\n");
    foreach (bit in bits)
    parse_comm(fd, bit);
}

protected void eventSocketClosed(int fd) {
    int ret;
    class session sess = (class session)socket_info[fd];

    if (sess && sess->data_fd != -1) {
        if ((ret = socket_close(sess->data_fd)) != EESUCCESS) {
            TPCRE("socket_close failed, reason: "+socket_error(ret)+"\n");
            ret = 0;
        }
        map_delete(data_sockets, sess->data_fd);
    }

    if (sess && sess->pasv_fd != -1) {
        if ((ret = socket_close(sess->pasv_fd)) != EESUCCESS) {
            TPCRE("socket_close failed, reason: "+socket_error(ret)+"\n");
            ret = 0;
        }
        map_delete(data_sockets, sess->pasv_fd);
    }

    map_delete(socket_info, fd);
}

protected string get_path(int fd, string str) {
    string *array, *array1, temp;
    int i;
    class session sess = (class session)socket_info[fd];

    if (!str || str == "") {
        /* no change of dir */
        return sess->cwd;
    }

    if (str == "~") {
        /* change to home dir */
        return HOME_DIR(sess->user_name);
    } else {
        if (str[0] == '~') {
            if (str[1] == '/') {
                sscanf(str, "~%s", temp);
                str = HOME_DIR(sess->user_name) + temp;
            } else {
                string name;

                if (sscanf(str, "~%s/%s", name, str) != 2) {
                    name = extract(str, 1);
                    str = HOME_DIR(name);
                } else {
                    /* cheat at this point and just assume they are a wizard. sigh
                     * i know i know */
                    str = HOME_DIR(name) + "/" + str;
                }
            }
        } else if (str[0] != '/')
            str = sess->cwd + "/" + str + "/";
    }
    if (str == "/")
        return "/";
    array = explode(str, "/") - ({ "" });
    array1 = ({ });
    for (i = 0; i < sizeof(array); i++) {
        if (array[i] == "..") {
            if (sizeof(array1)) {
                array1 = array1[0..<2];
            }
        } else if (array[i] != ".")
            array1 += ({ array[i] });
    }
    if (sizeof(array1))
        str = implode(array1, "/");
    else
        str = "";
    return "/" + str;
} /* get_path() */

protected string desc_object(mixed o) {
    string str;

    if (!o) return "** Null-space **";
    if (!catch(str = (string)o->short()) && str) return str;
    if (!catch(str = (string)o->query_name()) && str) return str;
    return file_name(o);
} /* desc_object() */

protected string desc_f_object(object o) {
    string str, tmp;

    str = desc_object(o);
    if (o && str != file_name(o)) {
        if (tmp)
            str += " (" + tmp + ")";
        else
            str += " (" + file_name(o) + ")";
    }
    return str;
} /* desc_f_object() */

protected string get_cfile(string str) {
    if (sscanf(str, "%*s.%*s") != 2)
        str += ".c";
    return str;
} /* get_cfile() */

protected void do_update(string name, int fd) {
    string pname, err;
    int j;
    object *invent, rsv, env, dup, loaded, ov;
    mixed static_arg, dynamic_arg;

    "room/void"->bingle_bingle();

    rsv = find_object("room/void");  /* RSV = Room Slash Void */
    if (!rsv) { /* Die in horror */
        eventWrite(fd, "530 The void is lost!\r\n");
        return;
    }
    name = get_cfile(name);
    ov = find_object(name);
    if (!ov) {
        if(file_size(name) >= 0) {
            if (!(err = catch(name->bing_with_me()))) {
                eventWrite(fd, sprintf("530 Loaded %s.\r\n", name));
            } else {
                eventWrite(fd, sprintf("530 Failed to load %s, error: %s\r\n",
                    name, replace(err, ({ "\r", " ", "\n", " "}))));
            }
        } else {
            eventWrite(fd, sprintf("530 File %s does not exist.\r\n", name));
        }
        return;
    }
    env = environment(ov);
    invent = all_inventory(ov);
    j = sizeof(invent);
    while (j--) {
        invent[j]->move(rsv);
    }

    pname = file_name(ov);
    if (sscanf(pname, "%s#%*d", pname) != 2) {
        /* a room ? */
        ov->dest_me();
        if (ov) {
            ov->dwep();
        }
        if (ov) {
            destruct(ov);
        }
        if (!ov) {
            ov = find_object(pname);
        }
        catch(call_other(pname, "??"));
        ov = find_object(pname);
    } else {
        loaded = find_object(pname);
        static_arg = ov->query_static_auto_load();
        dynamic_arg = ov->query_dynamic_auto_load();
        if (loaded) {
            loaded->dest_me();
        }
        if (loaded) {
            loaded->dwep();
        }
        if (loaded) {
            destruct(loaded);
        }

        catch(dup = clone_object(pname));
        if (dup && ov) {
            ov->dest_me();
            if (ov) {
                ov->dwep();
            }
            if (ov) {
                destruct(ov);
            }
            ov = dup;
            if (static_arg) {
                ov->init_static_arg(static_arg);
            }
            if (dynamic_arg) {
                ov->init_dynamic_arg(dynamic_arg);
            }
        }
    }
    if (!ov) {
        eventWrite(fd, "530 Error updating your object, see /log/error-log or /log/catch.\r\n");
        return;
    }

    j = sizeof(invent);
    while (j--) {
        if (invent[j]) {
            invent[j]->move(ov);
        }
    }

    if (env) {
        ov->move(env);
    }
    eventWrite(fd, sprintf("530 Updated %s.\r\n", desc_f_object(ov)));
} /* do_update() */

protected void check_connections() {
    int *bits, i;
    class session sess;

    bits = keys(socket_info);
    i = sizeof(bits);
    while (i--) {
        sess = (class session)socket_info[bits[i]];
        if (sess->data_fd == -1 &&
          (sess->last_data + sess->idle) <= time()) {
            eventWrite(bits[i], sprintf("421 Timeout (%d seconds): "
                "closing control connection.\r\n",
                sess->idle), 1);
        }
    }
    call_out("check_connections", 5 * 60);
} /* check_connections() */

protected int check_dots(mixed arg) {
    return (arg[0] != ".." && arg[0] != ".");
}
